package cn.wind.xboot.framework.filter;

import cn.hutool.crypto.asymmetric.KeyType;
import cn.wind.xboot.core.utils.AESUtil;
import cn.wind.xboot.core.utils.Assert;
import cn.wind.xboot.core.utils.RSAUtil;
import cn.wind.xboot.core.utils.UrlEncodedUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>Title: DecryptFilter</p>
 * <p>Description: TODO</p>
 *
 * @author xukk
 * @version 1.0
 * @date 2018/8/9
 */

public class DecryptFilter extends OncePerRequestFilter implements Ordered {
    private static AESUtil ECB = new AESUtil();
    private int order = 99;
    private Long timeLimit=5000L;
    private List<String> ignoreUrl = Lists.newArrayList();

    private static AntPathMatcher antPathMatcher = new AntPathMatcher();

    private StringRedisTemplate redisTemplate;

    public DecryptFilter() {
    }

    public DecryptFilter(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public DecryptFilter(String... ignoreUrl) {
        this.ignoreUrl = Arrays.stream(ignoreUrl).collect(Collectors.toList());
    }

    public DecryptFilter(StringRedisTemplate redisTemplate, String... ignoreUrl) {
        this.ignoreUrl = Arrays.stream(ignoreUrl).collect(Collectors.toList());
        this.redisTemplate = redisTemplate;
    }

    public void setTimeLimit(Long timeLimit) {
        this.timeLimit = timeLimit;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        for (int i = 0; i < ignoreUrl.size(); i++) {
            if (antPathMatcher.match(ignoreUrl.get(i), request.getRequestURI())) {
                filterChain.doFilter(request, response);
                return;
            }
        }
        if (!"GET".equalsIgnoreCase(request.getMethod()) && !checkFilter(request.getContentType())) {
            filterChain.doFilter(request, response);
            return;
        }
        Map<String, String[]> m = Maps.newHashMap();
        String keyType = request.getParameter("keyType");
        String key = request.getParameter("key");
        String encryptData = request.getParameter("data");
        String refresh = request.getParameter("refresh");
        String timestamp=request.getParameter("timestamp");
        String accessToken;
        RSAUtil rsa;
        String privateKey;
        String data = "";
        if (!StringUtils.isBlank(key)) {
            if (StringUtils.equalsIgnoreCase(keyType, "RSA")) {
                accessToken = request.getHeader("RandomStr");
                privateKey = redisTemplate.opsForValue().get("oauth:token:temp:" + accessToken);
            } else if (StringUtils.equalsIgnoreCase(refresh, "true")) {
                accessToken = request.getHeader("RandomStr");
                if (accessToken == null) {
                    accessToken = "";
                }
                privateKey = redisTemplate.opsForValue()
                        .get("oauth:token:rsa_refresh:" + accessToken.trim());

            } else {
                accessToken = request.getHeader("Authorization");
                privateKey = redisTemplate.opsForValue()
                        .get("oauth:token:rsa:" + accessToken.replace("Bearer", "")
                                .replace("bearer", "").trim());
            }
            String aesKey = null;
            rsa = RSAUtil.build().privateKey(privateKey).rsa();
            Assert.checkNotNull(!StringUtils.isBlank(timestamp),"时间戳不为空");
            timestamp=rsa.keyType(KeyType.PrivateKey).decrypt(new String(java.util.Base64.getUrlDecoder().decode(timestamp)));
            Long time=Long.valueOf(timestamp);
            Assert.checkArgument(time>0L&&Math.abs(time-System.currentTimeMillis())<timeLimit,"时间戳超时");
            if (!StringUtils.isEmpty(key) && !StringUtils.isEmpty(encryptData)) {
                encryptData = new String(java.util.Base64.getUrlDecoder().decode(encryptData));
                try {
                    aesKey = rsa.keyType(KeyType.PrivateKey).decrypt(new String(java.util.Base64.getUrlDecoder().decode(key)));
                    ECB.key(aesKey).algorithm(AESUtil.CONSTANT_ECB_ALGORITHM).build();
                    // 解密
                    data = ECB.decrypt(encryptData);
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    throw new RuntimeException("解密失败");
                }
            }
            UrlEncodedUtil.parse(data, false).forEach((k, v) -> m.put(k, new String[]{UrlEncodedUtil.decode(v)}));
            RequestWrapper requestWrapper = new RequestWrapper(request, m);
            requestWrapper.setAttribute("secure", true);
            requestWrapper.setAttribute("aesKey", aesKey);
            filterChain.doFilter(requestWrapper, response);
        } else {
            super.doFilter(request, response, filterChain);
        }
    }

    private boolean checkFilter(String contentType) {
        if (contentType != null) {
            MediaType mediaType = MediaType.parseMediaType(contentType);
            return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)
                    || MediaType.MULTIPART_FORM_DATA.includes(MediaType.parseMediaType(contentType));
        }
        return true;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Override
    public int getOrder() {
        return this.order;
    }
}